/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace Cobalt.Content.Pipeline.Graphics.Imaging
{

	public enum KernelType
	{
		Null,
		Sobel,
	}

	public sealed class Kernel2D
	{
		private int width;
		private float[] data;

		private Kernel2D( int width )
		{
			this.width = width;
			this.data = new float[width * width];
		}

		public Kernel2D( int width, KernelType type )
		{
			if( width < 0 )
				throw new ArgumentOutOfRangeException();

			this.width = width;
			this.data = new float[width * width];

			switch( type )
			{
				case KernelType.Null:
					break;

				case KernelType.Sobel:
					SetupSobel();
					break;

				default:
					throw new ArgumentException();
			}
		}

		public Kernel2D Clone()
		{
			Kernel2D ret = new Kernel2D( width );
			Array.Copy( data, ret.data, data.Length );
			return ret;
		}

		public void Normalize()
		{
			float total = 0;
			for( int i = 0; i < data.Length; i++ )
				total += (float)Math.Abs( data[i] );

			float inv = 1.0F / total;
			for( int i = 0; i < data.Length; i++ )
				data[i] *= inv;
		}

		public void Transpose()
		{
			for( int i = 0; i < data.Length; i++ )
				for( int j = i + 1; j < width; j++ )
					Helpers.Swap( ref data[i * width + j], ref data[j * width + i] );
		}

		public int Width { get { return width; } }
		public float this[int x, int y] { get { return data[y * width + x]; } }

		private static void Wrap( ref int x, ref int y, int width, int height, TextureAddressMode wrapMode )
		{
			switch( wrapMode )
			{
				case TextureAddressMode.Clamp:
					x = Helpers.Clamp( x, 0, width - 1 );
					y = Helpers.Clamp( y, 0, height - 1 );
					break;

				case TextureAddressMode.Wrap:
					x = x >= 0 ? x % width : (x + 1) % width + width - 1;
					y = y >= 0 ? y % height : (y + 1) % height + height - 1;
					break;

				case TextureAddressMode.Mirror:
					x = Math.Abs( x );
					while( x >= width )
						x = width + width - x - 2;

					y = Math.Abs( y );
					while( y >= height )
						y = height + height - y - 2;
					break;

				default:
					throw new ArgumentException();
			}
		}

		public float FilterImageData( float[][] image, int x, int y, TextureAddressMode wrapMode )
		{
			if( image.Length == 0 )
				return 0;

			int offset = width / 2 - 1;

			int imgw = image.Length;
			int imgh = image[0].Length;

			float sum = 0;
			for( int i = 0; i < width; i++ )
			{
				int sy = y + i - offset;

				for( int j = 0; j < width; j++ )
				{
					int ax = x + j - offset;
					int ay = sy;

					Wrap( ref ax, ref ay, imgw, imgh, wrapMode );

					sum += image[ax][ay] * this[j, i];
				}
			}

			return sum;
		}

		public Vector4 FilterImageData( Vector4[][] image, int x, int y, TextureAddressMode wrapMode )
		{
			if( image.Length == 0 )
				return Vector4.Zero;

			int offset = width / 2 - 1;

			int imgw = image.Length;
			int imgh = image[0].Length;

			Vector4 sum = Vector4.Zero;
			for( int i = 0; i < width; i++ )
			{
				int sy = y + i - offset;

				for( int j = 0; j < width; j++ )
				{
					int ax = x + j - offset;
					int ay = sy;

					Wrap( ref ax, ref ay, imgw, imgh, wrapMode );

					sum += image[ax][ay] * this[j, i];
				}
			}

			return sum;
		}

		#region Sobel
		private static readonly float[] Sobel3 = new float[]
			{
				-1, 0, 1,
				-2, 0, 2,
				-1, 0, 1,
			};
		private static readonly float[] Sobel5 = new float[]
			{
				-1, -2, 0, 2, 1,
				-2, -3, 0, 3, 2,
				-3, -4, 0, 4, 3,
				-2, -3, 0, 3, 2,
				-1, -2, 0, 2, 1,
			};
		private static readonly float[] Sobel7 = new float[]
			{
				-1, -2, -3, 0, 3, 2, 1,
				-2, -3, -4, 0, 4, 3, 2,
				-3, -4, -5, 0, 5, 4, 3,
				-4, -5, -6, 0, 6, 5, 4,
				-3, -4, -5, 0, 5, 4, 3,
				-2, -3, -4, 0, 4, 3, 2,
				-1, -2, -3, 0, 3, 2, 1,
			};
		private static readonly float[] Sobel9 = new float[]
			{
				-1, -2, -3, -4, 0, 4, 3, 2, 1,
				-2, -3, -4, -5, 0, 5, 4, 3, 2,
				-3, -4, -5, -6, 0, 6, 5, 4, 3,
				-4, -5, -6, -7, 0, 7, 6, 5, 4,
				-5, -6, -7, -8, 0, 8, 7, 6, 5,
				-4, -5, -6, -7, 0, 7, 6, 5, 4,
				-3, -4, -5, -6, 0, 6, 5, 4, 3,
				-2, -3, -4, -5, 0, 5, 4, 3, 2,
				-1, -2, -3, -4, 0, 4, 3, 2, 1,
			};

		private void SetupSobel()
		{
			if( width < 3 || (width % 2) != 1 )
				throw new ArgumentException();

			float[] preComp = null;
			switch( width )
			{
				case 3:
					preComp = Sobel3;
					break;
				case 5:
					preComp = Sobel5;
					break;
				case 7:
					preComp = Sobel7;
					break;
				case 9:
					preComp = Sobel9;
					break;
			}

			if( preComp != null )
			{
				Array.Copy( preComp, data, preComp.Length );
				return;
			}

			throw new ArgumentOutOfRangeException();
		}
		#endregion
	}

}